﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Drive.Support.Extensions
{
    public static class JoinExtension
    {
        public static IQueryable<TResult> LeftJoin<TTable1, TTable2, Tkey, TResult>(this
            IQueryable<TTable1> table1,
            IQueryable<TTable2> table2,
            Expression<Func<TTable1, Tkey>> keySelector,
            Expression<Func<TTable2, Tkey>> keySelector2,
            Expression<Func<JoinResult<TTable1, TTable2>, TResult>> resultSelector
            )
        {
            return table1.LeftJoin(table2, keySelector, keySelector2).JoinResult.Select(resultSelector);
        }
        public static JoinBuilder<TTable1, TTable2> LeftJoin<TTable1, TTable2, Tkey>(this
            IQueryable<TTable1> table1,
            IQueryable<TTable2> table2,
            Expression<Func<TTable1, Tkey>> keySelector,
            Expression<Func<TTable2, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2>
            {
                JoinResult = table1
                .GroupJoin(table2, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, TTable2>
                    {
                        Table1 = x,
                        Table2 = y.DefaultIfEmpty().FirstOrDefault()
                    }
             )
            };
            return builder;
        }

        public static JoinBuilder<TTable1, IEnumerable<TTable2>> LeftJoinOneToMany<TTable1, TTable2, Tkey>(this
            IQueryable<TTable1> table1,
            IQueryable<TTable2> table2,
            Expression<Func<TTable1, Tkey>> keySelector,
            Expression<Func<TTable2, Tkey>> keySelector2
            )
        {
            return new JoinBuilder<TTable1, IEnumerable<TTable2>>
            {
                JoinResult = table1
                .GroupJoin(table2, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, IEnumerable<TTable2>>
                    {
                        Table1 = x,
                        Table2 = y.DefaultIfEmpty()
                    }
                 )
            };
        }
    }
    public interface IJoinResult
    {

    }

    [DebuggerDisplay("{Field} {Op} {Value}")]
    public class JoinCondition
    {
        public TableField Field { get; set; }
        public string Op { get; set; }
        public object Value { get; set; }

    }

    [DebuggerDisplay("parameter({Value})")]
    public class JoinConditionParameterizedValue
    {
        public string Value { get; set; }
        public string PrameterName { get; set; }
    }


    [DebuggerDisplay("value({Value})")]
    public class JoinConditionConstantValue
    {
        public string Value { get; set; }
    }

    [DebuggerDisplay("field({RefField})")]
    public class JoinConditionValueRef
    {
        public TableField RefField { get; set; }
    }

    [DebuggerDisplay("{FieldName}")]
    public class TableField
    {
        public string Schema { get; set; }
        public string Table { get; set; }
        public string FieldName { get; set; }
        public Type TableType { get; private set; }

        public static TableField FromSelector(Expression expression, string tableName = null)
        {
            var filed = new TableField() { Table = tableName };
            new ParameterVisitor(filed).Visit(expression);
            return filed;
        }

        public class ParameterVisitor : ExpressionVisitor
        {
            private TableField tableField;

            public ParameterVisitor(TableField tableField)
            {
                this.tableField = tableField;
            }

            protected override Expression VisitMember(MemberExpression node)
            {
                if (tableField.FieldName != null)
                {
                    tableField.FieldName = node.Member.Name + "." + tableField.FieldName;
                }
                else
                {
                    tableField.FieldName = node.Member.Name;
                }

                if (node.Member.Name.StartsWith("Table"))
                {

                }
                return base.VisitMember(node);
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (typeof(IJoinResult).IsAssignableFrom(node.Type))
                {

                }
                this.tableField.TableType = node.Type;
                return base.VisitParameter(node);
            }
        }
    }

    public class JoinQueryable<TElement> : IQueryable<TElement>
    {
        public Expression Expression { get; set; }

        public Type ElementType { get; set; }

        public IQueryProvider Provider { get; set; }

        public IEnumerator<TElement> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

    public class JoinQueryable : IQueryable
    {
        public Expression Expression { get; set; }

        public Type ElementType { get; set; }

        public IQueryProvider Provider { get; set; }

        public IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

    public class JoinQueryProvider : IQueryProvider
    {
        public IQueryable CreateQuery(Expression expression)
        {
            return new JoinQueryable { Provider = this, Expression = expression };
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new JoinQueryable<TElement> { Provider = this, Expression = expression };
        }

        public object Execute(Expression expression)
        {
            throw new NotImplementedException();
        }

        public TResult Execute<TResult>(Expression expression)
        {
            throw new NotImplementedException();
        }
    }

    public class JoinBuilder
    {

    }

    public class JoinBuilder<TTable1, TTable2> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2>> JoinResult { get; set; }

        public IQueryable<TResult> Select<TResult>(Expression<Func<JoinResult<TTable1, TTable2>, TResult>> selector)
        {
            return this.JoinResult.Select(selector);
        }

        public JoinBuilder<TTable1, TTable2> Where(Expression<Func<JoinResult<TTable1, TTable2>, bool>> predicate)
        {
            this.JoinResult = this.JoinResult.Where(predicate);
            return this;
        }

        public List<JoinResult<TTable1, TTable2>> ToList()
        {
            return this.JoinResult.ToList();
        }

        public JoinBuilder<TTable1, TTable2> WhereOr(List<Expression<Func<JoinResult<TTable1, TTable2>, bool>>> expressions)
        {
            Expression<Func<JoinResult<TTable1, TTable2>, bool>> exp = null;
            foreach (var item in expressions)
            {
                if (exp == null)
                {
                    exp = item;
                }
                else
                {
                    exp = Expression.Lambda<Func<JoinResult<TTable1, TTable2>, bool>>(
                         Expression.OrElse(
                             exp.Body,
                             new ExpressionParameterReplacer(item.Parameters, exp.Parameters).Visit(item.Body)),
                         exp.Parameters);
                }
            }
            this.JoinResult = JoinResult.Where(exp);
            return this;
        }
        public class ExpressionParameterReplacer : ExpressionVisitor
        {
            public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
            {
                ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
                for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                    ParameterReplacements.Add(fromParameters[i], toParameters[i]);
            }
            private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements
            {
                get;
                set;
            }
            protected override Expression VisitParameter(ParameterExpression node)
            {
                ParameterExpression replacement;
                if (ParameterReplacements.TryGetValue(node, out replacement))
                    node = replacement;
                return base.VisitParameter(node);
            }
        }

        public JoinResult<TTable1, TTable2> FirstOrDefault()
        {
            return this.JoinResult.FirstOrDefault();
        }

        public JoinResult<TTable1, TTable2> FirstOrDefault(Expression<Func<JoinResult<TTable1, TTable2>, bool>> predicate)
        {
            return this.JoinResult.FirstOrDefault(predicate);
        }

        public JoinBuilder<TTable1, TTable2, TTable3> LeftJoin<TTable3, Tkey>(
            IQueryable<TTable3> table3,
            Expression<Func<JoinResult<TTable1, TTable2>, Tkey>> keySelector,
            Expression<Func<TTable3, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3>
            {
                JoinResult = this.JoinResult.GroupJoin(table3, keySelector, keySelector2, (x, y) =>
                new JoinResult<TTable1, TTable2, TTable3>
                {
                    Table1 = x.Table1,
                    Table2 = x.Table2,
                    Table3 = y.DefaultIfEmpty().FirstOrDefault()
                }
             )
            };
            return builder;
        }

        public JoinBuilder<TTable1, TTable2, TTable3> LeftJoin<TTable3>(
            IQueryable<TTable3> table,
            Expression<Func<JoinResult<TTable1, TTable2>, object>> keySelector,
            Expression<Func<TTable3, object>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                new JoinResult<TTable1, TTable2, TTable3>
                {
                    Table1 = x.Table1,
                    Table2 = x.Table2,
                    Table3 = y.DefaultIfEmpty().FirstOrDefault()
                }
             )
            };
            return builder;
        }

        public JoinBuilder<TTable1, TTable2, IEnumerable<TTable3>> LeftJoinOneToMany<TTable3, Tkey>(
            IQueryable<TTable3> table,
            Expression<Func<JoinResult<TTable1, TTable2>, Tkey>> keySelector,
            Expression<Func<TTable3, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, IEnumerable<TTable3>>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                new JoinResult<TTable1, TTable2, IEnumerable<TTable3>>
                {
                    Table1 = x.Table1,
                    Table2 = x.Table2,
                    Table3 = y.DefaultIfEmpty()
                }
             )
            };
            return builder;
        }
    }
    public class JoinBuilder<TTable1, TTable2, TTable3> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2, TTable3>> JoinResult { get; set; }

        public IQueryable<TResult> Select<TResult>(Expression<Func<JoinResult<TTable1, TTable2, TTable3>, TResult>> selector)
        {
            return this.JoinResult.Select(selector);
        }
        public JoinBuilder<TTable1, TTable2, TTable3> Where(Expression<Func<JoinResult<TTable1, TTable2, TTable3>, bool>> predicate)
        {
            this.JoinResult = this.JoinResult.Where(predicate);
            return this;
        }

        public List<JoinResult<TTable1, TTable2, TTable3>> ToList()
        {
            return this.JoinResult.ToList();
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4> LeftJoin<TTable4, Tkey>(
            IQueryable<TTable4> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3>, Tkey>> keySelector,
            Expression<Func<TTable4, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, TTable2, TTable3, TTable4>
                    {
                        Table1 = x.Table1,
                        Table2 = x.Table2,
                        Table3 = x.Table3,
                        Table4 = y.DefaultIfEmpty().FirstOrDefault()
                    }
             )
            };
            return builder;
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4> LeftJoinOneToMany<TTable4, Tkey>(
            IQueryable<TTable4> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3>, Tkey>> keySelector,
            Expression<Func<TTable4, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) => new
                {
                    PrevJoinResult = x,
                    Table4 = y.DefaultIfEmpty()
                })
                .SelectMany(x => x.Table4.DefaultIfEmpty(), (x, y) => new JoinResult<TTable1, TTable2, TTable3, TTable4>
                {
                    Table1 = x.PrevJoinResult.Table1,
                    Table2 = x.PrevJoinResult.Table2,
                    Table3 = x.PrevJoinResult.Table3,
                    Table4 = y,
                })
            };
            return builder;
        }
    }

    public class JoinBuilder<TTable1, TTable2, TTable3, TTable4> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2, TTable3, TTable4>> JoinResult { get; set; }


        public IQueryable<TResult> Select<TResult>(Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4>, TResult>> selector)
        {

            return this.JoinResult.Select(selector);
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5> LeftJoin<TTable5, Tkey>(
            IQueryable<TTable5> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4>, Tkey>> keySelector,
            Expression<Func<TTable5, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5>
                    {
                        Table1 = x.Table1,
                        Table2 = x.Table2,
                        Table3 = x.Table3,
                        Table4 = x.Table4,
                        Table5 = y.DefaultIfEmpty().FirstOrDefault()
                    }
             )
            };
            return builder;
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4, IEnumerable<TTable5>> LeftJoinOneToMany<TTable5, Tkey>(
            IQueryable<TTable5> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4>, Tkey>> keySelector,
            Expression<Func<TTable5, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4, IEnumerable<TTable5>>
            {


                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) => new
                {
                    PrevJoinResult = x,
                    Table5 = y.DefaultIfEmpty()
                })
                .Select(x => new JoinResult<TTable1, TTable2, TTable3, TTable4, IEnumerable<TTable5>>
                {
                    Table1 = x.PrevJoinResult.Table1,
                    Table2 = x.PrevJoinResult.Table2,
                    Table3 = x.PrevJoinResult.Table3,
                    Table4 = x.PrevJoinResult.Table4,
                    Table5 = x.Table5,
                })
            };
            return builder;
        }
    }


    public class JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5>> JoinResult { get; set; }


        public IQueryable<TResult> Select<TResult>(Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5>, TResult>> selector)
        {

            return this.JoinResult.Select(selector);
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6> LeftJoin<TTable6, Tkey>(
            IQueryable<TTable6> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5>, Tkey>> keySelector,
            Expression<Func<TTable6, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6>
            {

                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6>
                    {
                        Table1 = x.Table1,
                        Table2 = x.Table2,
                        Table3 = x.Table3,
                        Table4 = x.Table4,
                        Table5 = x.Table5,
                        Table6 = y.DefaultIfEmpty().FirstOrDefault()
                    }
             )
            };
            return builder;
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, IEnumerable<TTable6>> LeftJoinOneToMany<TTable6, Tkey>(
            IQueryable<TTable6> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5>, Tkey>> keySelector,
            Expression<Func<TTable6, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, IEnumerable<TTable6>>
            {
                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) => new
                {
                    PrevJoinResult = x,
                    Table6 = y.DefaultIfEmpty()
                })
                .Select(x => new JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, IEnumerable<TTable6>>
                {
                    Table1 = x.PrevJoinResult.Table1,
                    Table2 = x.PrevJoinResult.Table2,
                    Table3 = x.PrevJoinResult.Table3,
                    Table4 = x.PrevJoinResult.Table4,
                    Table5 = x.PrevJoinResult.Table5,
                    Table6 = x.Table6
                })
            };
            return builder;
        }

    }

    public class JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6>> JoinResult { get; set; }

        public IQueryable<TResult> Select<TResult>(Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6>, TResult>> selector)
        {

            return this.JoinResult.Select(selector);
        }

        public JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7> LeftJoin<TTable7, Tkey>(
            IQueryable<TTable7> table,
            Expression<Func<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6>, Tkey>> keySelector,
            Expression<Func<TTable7, Tkey>> keySelector2
            )
        {
            var builder = new JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7>
            {

                JoinResult = this.JoinResult.GroupJoin(table, keySelector, keySelector2, (x, y) =>
                    new JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7>
                    {
                        Table1 = x.Table1,
                        Table2 = x.Table2,
                        Table3 = x.Table3,
                        Table4 = x.Table4,
                        Table5 = x.Table5,
                        Table6 = x.Table6,
                        Table7 = y.DefaultIfEmpty().FirstOrDefault()
                    }
             )
            };
            return builder;
        }
    }

    public class JoinBuilder<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7> : JoinBuilder
    {
        public IQueryable<JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7>> JoinResult { get; set; }
    }

    public class JoinResult<TTable1, TTable2> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
    }

    public class JoinResult<TTable1, TTable2, TTable3> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
    }

    public class JoinResult<TTable1, TTable2, TTable3, TTable4> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
        public TTable4 Table4 { get; set; }
    }


    public class JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
        public TTable4 Table4 { get; set; }
        public TTable5 Table5 { get; set; }
    }

    public class JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
        public TTable4 Table4 { get; set; }
        public TTable5 Table5 { get; set; }
        public TTable6 Table6 { get; set; }
    }

    public class JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
        public TTable4 Table4 { get; set; }
        public TTable5 Table5 { get; set; }
        public TTable6 Table6 { get; set; }
        public TTable7 Table7 { get; set; }
    }

    public class JoinResult<TTable1, TTable2, TTable3, TTable4, TTable5, TTable6, TTable7, TTable8> : IJoinResult
    {
        public TTable1 Table1 { get; set; }
        public TTable2 Table2 { get; set; }
        public TTable3 Table3 { get; set; }
        public TTable4 Table4 { get; set; }
        public TTable5 Table5 { get; set; }
        public TTable6 Table6 { get; set; }
        public TTable7 Table7 { get; set; }
        public TTable8 Table8 { get; set; }
    }
}
